// basic_process.h

#ifndef DODO_BASIC_PROCESS_H
#define DODO_BASIC_PROCESS_H

#include <stdexcept>
#include <cmath>
#include <numeric>
#include <algorithm>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <quigon.h>


namespace quigon
{
template<class T1, class T2>
void Example(T1& line, const T2& arg)
{
	for (typename T1::iterator i = line.begin(); i != line.end(); ++i)
	{
		*i = 1.234;
	}
}
// required fields in para:
// Link_Stuff<Lidar_Profile>	"afterpulse", "overlap",
// Lidar_Profile	"distance",
// Link_Stuff<Time_Serie>		"lidar_constant",
// Lidar_Cross_Ref	"distance_i"
template<class Tline, class Tpara>
class Correction
{
public:
	//default to do everything, and make every minus value 0.
	Correction(
	        const Tpara& para0,
	        int option =
	                Status::BACKGROUND |
	                Status::AFTERPULSE |
	                Status::DISTANCE   |
	                Status::OVERLAP    |
	                Status::ENERGY	   |
	                Status::LC		   ,
	        bool kick_minus0 = true
	) :
			total_option(option),
			b_bg(option & Status::BACKGROUND),
			b_ap(option & Status::AFTERPULSE),
			b_di(option & Status::DISTANCE),
			b_ol(option & Status::OVERLAP),
			b_en(option & Status::ENERGY),
			b_lc(option & Status::LC),
			kick_minus(kick_minus0),
			para(para0)
	{
	}

	bool operator()(Tline& line)
	{
		try
		{
			// should add some verification here.
			//
			if (! line.is_valid())
				return true;

			int status = line.status();

			bool l_bg = status & Status::BACKGROUND;
			bool l_ap = status & Status::AFTERPULSE;
			bool l_di = status & Status::DISTANCE;
			bool l_ol = status & Status::OVERLAP;
			bool l_en = status & Status::ENERGY;
			bool l_lc = status & Status::LC;

			typename Tline::attr_type background = line["background"];
			typename Tline::attr_type energy = line["energy"];

			size_t afterpulse_size = para.afterpulse().size();  // afterpulse is Link_Stuff<Lidar_Profile>
			size_t overlap_size = para.overlap().size();		// overlap is Link_Stuff<Lidar_Profile>

			for (size_t i = 0; i < line.size(); ++i)
			{
				if (b_bg && !l_bg)
				{
					line[i] -= background;
				}
				if (b_ap && !l_ap)
				{
					if (i < afterpulse_size)
						line[i] -= para.afterpulse()[i] * energy;		// Link_Stuff
					// notice that afterpulse may need to multiply energy
					// depending on how that coefficient is defined.
				}
				if (b_di && !l_di)
				{
					line[i] *= para.distance[i] * para.distance[i];
					// everything's kilometer
				}
				if (b_ol && !l_ol)
				{
					if (i < overlap_size)
						line[i] *= para.overlap()[i];			// Link_Stuff
					// our overlap coef is actually ( 1 / Oc(z) ), so multiply it here.
				}
				if (b_en && !l_en)
				{
					line[i] /= energy;
				}
				if (b_lc && !l_lc)
				{
					line[i] /= para.lidar_constant()[line.t()]; // lidar_constant is Link_Stuff<Time_Serie>
				}
				if (kick_minus && line[i] < 0.0)
				{
					line[i] = 0.0;
				}
			}

			line.status() |= total_option;

			return true;
		}
		catch (...)
		{
			func_err("Correction::operator()");
			throw;
		}
	}
protected:
	int total_option;
	bool b_bg, b_ap, b_di, b_ol, b_en, b_lc;
	bool kick_minus ;

	const Tpara& para;

};

template < class Tline, class Iter = typename std::list<Tline>::const_iterator >
class Basic_Average
{
public:
	Basic_Average(const typename std::vector<std::string>& aver_attr0, bool cover_gap0 = false)
			: aver_attr(aver_attr0), cover_gap(cover_gap0) {}

	Tline operator()(Iter beg, Iter end) const
	{
		try
		{
			if (beg == end)
				return Tline();

			while (beg->status() & Status::INVALID)
				++beg;
			Tline result(*beg);
			long res_milli_duration = result.duration().total_milliseconds();

			for (size_t i = 0; i < result.size(); ++i)
			{
				result[i] *= res_milli_duration;
			}
			for (size_t j = 0; j < aver_attr.size(); ++j)
			{
				result[aver_attr[j]] *= res_milli_duration;
			}

			++beg;
			for (Iter iter = beg; iter != end; ++iter)
			{
				if (!iter->is_valid())
					continue;
				result.duration() += iter->duration();
				long milli_duration = iter->duration().total_milliseconds();
				res_milli_duration += milli_duration;
				for (size_t i = 0; i < result.size(); ++i)
				{
					result[i] += (*iter)[i] * milli_duration;
				}
				for (size_t j = 0; j < aver_attr.size(); ++j)
				{
					result[aver_attr[j]] += (*iter)[aver_attr[j]] * milli_duration;
				}
			}

			for (size_t i = 0; i < result.size(); ++i)
			{
				result[i] /= res_milli_duration;
			}
			for (size_t j = 0; j < aver_attr.size(); ++j)
			{
				result[aver_attr[j]] /= res_milli_duration;
			}

			if (cover_gap)
			{
				--end;
				result.duration() = end->t() + end->duration() - result.t();
			}
			return result;
		}
		catch (...)
		{
			func_err("Basic_Average::operator()");
			throw;
		}
	}

private:
	const std::vector<std::string>& aver_attr;
	bool cover_gap;
};

template<class Tline>
class Average_Machine
{
public:
	Average_Machine(double seconds0, const Basic_Average<Tline>& aver0)
			: aver(aver0), milli(static_cast<long>(seconds0 * 1000)) {}
	Average_Machine(double seconds0, const std::vector<std::string>& aver_attr0, bool cover_gap0 = false)
			: aver(aver_attr0, cover_gap0), milli(static_cast<long>(seconds0 * 1000)) {}
	bool operator()(std::list<Tline>& old)
	{
		try
		{
			using namespace boost::posix_time;
			if (old.size() == 0)
				return true;

			std::list<Tline> result;

			ptime start_point = old.begin()->t();
			ptime end_point = (-- (old.end()))->t() + (-- (old.end()))->duration();
			time_period scanner(start_point, milliseconds(milli));
			typename std::list<Tline>::const_iterator i1 = old.begin();
			typename std::list<Tline>::const_iterator i2 ;
			for (i2 = old.begin() ; i2 != old.end(); ++i2)
			{
				if (scanner.contains(i2->t()))
					continue;

				result.push_back(aver(i1, i2));
				i1 = i2;
				while (! scanner.contains(i1->t()))       // shift until we got i1 again.
					scanner.shift(milliseconds(milli));
			}
			result.push_back(aver(i1, i2));

			old.clear();
			old.merge(result);

			return true;
		}
		catch (...)
		{
			func_err("Average_Machine::operator()");
			throw;
		}
	}
private:
	Basic_Average<Tline> aver;
	long milli;
};

// require para fields:
// size_t	"first_usable_index"
// size_t	"zero_meter_index"
class Fill_Lowest
{
public:
	Fill_Lowest(size_t zero_meter_index0, size_t first_usable_index0, size_t n_sample0)
			: zero_meter_index(zero_meter_index0), first_usable_index(first_usable_index0), n_sample(n_sample0)
	{}

	template<class Tpara>
	Fill_Lowest(const Tpara& para, size_t n_sample0)
			: zero_meter_index(para.zero_meter_index),
			first_usable_index(para.first_usable_index),
			n_sample(n_sample0)
	{}

	template<class Tline>
	bool operator()(Tline& line) const
	{
		try
		{
			if (! line.is_valid())
				return true;
			// avoiding too large sample number.
			size_t real_n_sample =
			        n_sample <= line.size() - first_usable_index ? n_sample : line.size() - first_usable_index;
			if (real_n_sample <= 0)
				return false;

			typename Tline::value_type fill_value =
			        std::accumulate(line.begin() + first_usable_index,
			                        line.begin() + first_usable_index + real_n_sample, 0.0)
			        / real_n_sample;
			fill(line.begin() + zero_meter_index, line.begin() + first_usable_index, fill_value);

			return true;
		}
		catch (...)
		{
			func_err("Fill_Lowest::operator()");
			throw;
		}
	}

private:
	size_t zero_meter_index;
	size_t first_usable_index;
	size_t n_sample;
};

template<class Tline>
bool fill_time_gap(std::list<Tline>& container, double fix_seconds = -999.9)
{
	using namespace std;
	using namespace boost::posix_time;
	if (container.empty() || container.size() == 1)
		return true;

	typename list<Tline>::iterator iter = container.begin();
	ptime last_time(iter->t());
	time_duration last_duration(iter->duration());
	if (fix_seconds > 0.0)
		last_duration = milliseconds(static_cast<long>(fix_seconds * 1000.0));

	Tline temp(*iter);
	temp.reset_value();
	temp.reset_attr();
	temp.status() = Status::INVALID;
	//not nececcary, because the default constructor should have done this.

	++iter;
	while (iter != container.end())
	{
		if ((iter->t() - last_time) >= last_duration * 2)
		{
			temp.t() = last_time + last_duration;
			temp.duration() = last_duration;

			container.insert(iter, temp);
			last_time = temp.t();
			// last_duration remains the same.
		}
		else
		{
			last_time = iter->t();
			if (fix_seconds <= 0.0)
				last_duration = iter->duration();
			++iter;
		}
	}
	return true;
}

template<class Tline>
bool kick_minus(Tline &line, double limit = 0)
{
	for (typename Tline::iterator iter = line.begin(); iter != line.end(); ++ iter)
	{
		if (*iter < limit)
			*iter = limit;
	}
	return true;
}
}

#endif

